#include "TrueText/Config.h"
#include "Layouter.h"
#include "FontImpl.h"
#include "TrueText/Config.h"
#include "AutoGlyph.h"

#include "ft2build.h"
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_CACHE_H

#include "TextUtils.h"
#include "ftutils.h"
#include "ftcache.h"

namespace TrueText {

Dimensions Layouter::MeasureSymbol(wchar_t ch) const
{
	FT_UInt gindex = FTC_CMapCache_Lookup(
		GetCMapCache(),
		reinterpret_cast<FTC_FaceID>(font->getFaceId()),
		-1,
		ch);

	FTC_ScalerRec sr = this->font->getScaler();
	FT_Glyph glyph;
	FT_EH(FTC_ImageCache_LookupScaler(GetImageCache(), &sr, FT_LOAD_NO_BITMAP, gindex, &glyph, 0));

	return Dimensions((int)(glyph->advance.x / 1024.0 / (double)FT_Units_In_Pixel), (int)font->GetLineHeight());
}

WordMetrics Layouter::MeasureWord(const std::wstring& word) const
{
	FT_Size size = this->font->getSize();
	FTC_ScalerRec sr = this->font->getScaler();

	bool use_kerning = FT_HAS_KERNING(size->face) != 0;
	FT_UInt previous = 0;

	// Calculate the bounding box
	FT_BBox bbox;
	bbox.xMin = bbox.yMin = LONG_MAX;
	bbox.xMax = bbox.yMax = LONG_MIN;

	FT_Pos baselinePos = 0;
	FT_Pos currentline = 0;
	FT_Pos pen_x = 0;
	FT_Pos pen_y = 0;
	std::vector<int> letterPositions;
	letterPositions.reserve(word.size());
	for (size_t n = 0; n < word.size(); ++n)
	{
		if(word[n] == '\r')
			continue;

		if(word[n] == '\n')
			continue;

		letterPositions.push_back(pen_x / FT_Units_In_Pixel);

		//adjust for kerning
		FT_UInt glyph_index = FTC_CMapCache_Lookup(
			GetCMapCache(),
			reinterpret_cast<FTC_FaceID>(font->getFaceId()),
			-1,
			word[n]);

		if (use_kerning && previous && glyph_index)
		{
			FT_Vector delta;
			FT_EH(FT_Get_Kerning(size->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta));

			pen_x += delta.x;
		}

		FT_Glyph glyph;
		FT_EH(FTC_ImageCache_LookupScaler(GetImageCache(), &sr, FT_LOAD_NO_BITMAP, glyph_index, &glyph, 0));
		//get bounding box
		FT_BBox glyph_bbox;
		FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_gridfit, &glyph_bbox);

		glyph_bbox.xMin += pen_x;
		glyph_bbox.xMax += pen_x;
		glyph_bbox.yMin += pen_y;
		glyph_bbox.yMax += pen_y;

		if (baselinePos < -glyph_bbox.yMin)
			baselinePos = -glyph_bbox.yMin;

		if (glyph_bbox.xMin < bbox.xMin)
			bbox.xMin = glyph_bbox.xMin;
		if (glyph_bbox.yMin < bbox.yMin)
			bbox.yMin = glyph_bbox.yMin;
		if (glyph_bbox.xMax > bbox.xMax)
			bbox.xMax = glyph_bbox.xMax;
		if (glyph_bbox.yMax > bbox.yMax)
			bbox.yMax = glyph_bbox.yMax;

		pen_x += glyph->advance.x >> 10;
		pen_y += glyph->advance.y >> 10;

		previous = glyph_index;
	}

	if (bbox.xMin > bbox.xMax)
	{
		bbox.xMin = bbox.yMin =
		bbox.xMax = bbox.yMax = 0;
	}

	fast_float width = (bbox.xMax - bbox.xMin) / (fast_float)FT_Units_In_Pixel;
	fast_float height = (bbox.yMax - bbox.yMin) / (fast_float)FT_Units_In_Pixel;
	fast_float voffset = bbox.yMax / (fast_float)FT_Units_In_Pixel;

	WordMetrics metrics;
	metrics.Bounds.Width = (int)width;
	metrics.Bounds.Height = (int)height;
	metrics.Baseline = (int)(baselinePos / (fast_float)FT_Units_In_Pixel);
	metrics.VOffset = (int)voffset;
	metrics.LetterPositions = letterPositions;
	metrics.BearingX = bbox.xMin / FT_Units_In_Pixel;

	return metrics;
}

// Append "..." text to the given line.
void Layouter::AppendDots(Line& line, LineMetrics& metrics) const
{
	static std::wstring dots = L"...";

	Word dotsWord;
	dotsWord.IsEnterchar = false;
	dotsWord.IsWhitespace = false;
	dotsWord.WordLength = 3;
	dotsWord.WordStart = dots.c_str();

	WordMetrics dotsMetrics;
	dotsMetrics = MeasureWord(dots);

	line.Words.push_back(dotsWord);
	metrics.Words.push_back(dotsMetrics);
	metrics.Width += dotsMetrics.Bounds.Width;
}


void GetWord(Word& result, wchar_t const*& szText)
{
	if (szText)
	{
		result.IsEnterchar = TextUtils::IsEnterchar(*szText);
		result.IsWhitespace = TextUtils::IsWhitespace(*szText);
		result.WordLength = 0;
		result.WordStart = szText;
		if (result.IsEnterchar)
		{
			if (*szText == 0x0D && *(szText + 1) == 0x0A)
				++szText;
			++szText;
			++result.WordLength;
		}
		else
		{
			if (result.IsWhitespace)
			{
				while (*szText && TextUtils::IsWhitespace(*szText))
				{
					++szText;
					++result.WordLength;
				}
			}
			else
			{
				while (*szText && !TextUtils::IsEnterchar(*szText) && !TextUtils::IsWhitespace(*szText))
				{
					++szText;
					++result.WordLength;
				}
			}
		}
	}
}

void GetWords(std::list<Word>& result, wchar_t const* szText)
{
	while (*szText)
	{
		Word word;
		GetWord(word, szText);
		result.push_back(word);
	}
}

boost::shared_ptr<TextLayout> Layouter::Layout(int clipWidth, const wchar_t* text, TextAlign align)
{
	// Check preconditions:
	if (!font.get())
		TRUE_TEXT_THROW(Error(L"Assertion failed. Unable to layout text without a font."));

	// Do the job:
	boost::shared_ptr<TextLayout> result(new TextLayout());
	result->Str = text;
	result->Lines.clear();
	result->Metrics.clear();
	result->LineHeight = (int)font->GetLineHeight();

	std::list<Word> words;
	GetWords(words, result->Str.c_str());
	result->Lines.push_back(Line());
	result->Metrics.push_back(LineMetrics());

	fast_float totalWidth = 0;
	fast_float totalHeight = font->GetLineHeight();

	for (std::list<Word>::iterator word = words.begin(); word != words.end(); ++word)
	{
		if (word->IsEnterchar)
		{
			/*if (dwFlags & dtfShortText)
			{
				AppendDots(result.Lines.back(), result.Metrics.back());
				break;
			}*/

			if(result->Metrics.back().Width > totalWidth)
				totalWidth = (fast_float)result->Metrics.back().Width;

			totalHeight += font->GetLineHeight();
			result->Lines.push_back(Line());
			result->Metrics.push_back(LineMetrics());

			continue;
		}

		WordMetrics wordMetrics;

		if (word->IsWhitespace)
			wordMetrics.Bounds.Width = MeasureSymbol(L' ').Width * word->WordLength;
		else
		{
			std::wstring text(word->WordStart, word->WordStart + word->WordLength);
			wordMetrics = MeasureWord(text);
		}

		// TODO: somebody may wish to have true here.
		bool skipWrapping = clipWidth < 0;
		if (skipWrapping || result->Metrics.back().Width + wordMetrics.Bounds.Width < clipWidth)
		{
			result->Lines.back().Words.push_back(*word);
			result->Metrics.back().Words.push_back(wordMetrics);
			result->Metrics.back().Width += wordMetrics.Bounds.Width;
		}
		else
		{
			if (result->Metrics.back().Width == 0.0f)
			{
				result->Lines.back().Words.push_back(*word);
				result->Metrics.back().Words.push_back(wordMetrics);
			}
			else if (!word->IsWhitespace)
			{
				--word;
			}

			/*if (dwFlags & dtfShortText)
			{
				AppendDots(result.Lines.back(), result.Metrics.back());
				break;
			}*/

			if(result->Metrics.back().Width > totalWidth)
				totalWidth = (fast_float)result->Metrics.back().Width;

			totalHeight += font->GetLineHeight();
			result->Lines.push_back(Line());
			result->Metrics.push_back(LineMetrics());
		}
	}
	
	// compute maximum line length in array
	int maxLength = 0;
	for(std::list<LineMetrics>::iterator lineMetric = result->Metrics.begin(); lineMetric != result->Metrics.end(); ++lineMetric)
		maxLength = std::max(maxLength, lineMetric->Width);
	
	// compute offset for each line
	for(std::list<LineMetrics>::iterator lineMetric = result->Metrics.begin(); lineMetric != result->Metrics.end(); ++lineMetric)
	{
		// compute
		int offset = 0;
		if(align == CENTER)
		{
			offset = (maxLength - lineMetric->Width) / 2;
		}
		else if(align == RIGHT)
		{
			offset = maxLength - lineMetric->Width;
		}
		
		// apply to all words
		for(std::list<WordMetrics>::iterator word = lineMetric->Words.begin(); word != lineMetric->Words.end(); ++word)
		{
			for(int i = 0; i < word->LetterPositions.size(); i++)
				word->LetterPositions[i] += offset;
		}
	}

	return result;
}

}
